package com.dippy.shiro;


import cn.hutool.json.JSONUtil;
import com.dippy.common.lang.Result;
import com.dippy.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/*
需要解决跨域问题
    在所有方法执行之前解决
    preHandle方法会在所以方法执行之前执行，所以重写这个方法，将解决跨域问题放在这方法里
 */

/**
 * 获取jwt进行登录处理
 * 若没有jwt则调到登录过滤那里处理
 * 有jwt则交给shiro处理，shiro封装成token
 * 区别
 * AuthenticatingFilter : 自动登陆逻辑
 * BasicHttpAuthenticationFilter：封装更彻底
 */
@Component
public class JwtFilter extends AuthenticatingFilter {

    //导入jwtUtil用于生产jwt、判断jwt
    @Autowired
    private JwtUtils jwtUtils;


    /**
     * 生成token后 通过 executeLogin 交给shiro 进入 自定义的AccountRealm中的认证方法，判断token正不正确
     * executeLogin 交给shiro 进入
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        // 从request的头中获取jwt，
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        // 获取请求头
        String jwt = servletRequest.getHeader("Authorization");

        // 如果没有jwt,即jwt为空
        if (StringUtils.isEmpty(jwt)) {
            return null;// 跳过？
        }
        // 有jwt就封装成token形式的
        return new JwtToken(jwt);
    }

    /**
     * onAccessDenied为拒绝访问，
     * 返回true 拒绝访问
     * 返回false 可以访问
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // 判断jwt是否异常

        // 从request的头中获取jwt，
        HttpServletRequest Request1 = (HttpServletRequest) request;
        // 获取请求头
        String jwt = Request1.getHeader("Authorization");

        // 如果没有jwt,即jwt为空，没有登录拒绝访问，接着查看有无访问的权限
        if (StringUtils.isEmpty(jwt)) {
            // 直接跳过jwt认证，即跳过登录或者其他，检验是否有权限访问页面
            // 即不拦截，
            return true;
        }
        // jwt不为空时

        // 1. 校验jwt,异常为否
        Claims claim = jwtUtils.getClaimByToken(jwt);
        if (claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
            throw new ExpiredCredentialsException("token已失效，请重新登录");
        }

        // jwt正常
        // 2. 执行登录
        return executeLogin(request,response);
    }

    /**
     * 登录时出现异常
     * 重写登录错误原因的方法
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {

        HttpServletResponse response1 = (HttpServletResponse) response;

        // 获取错误原因
        Throwable throwable = e.getCause() == null ? e : e.getCause();

        // 返回给前端
        Result result = Result.fail(throwable.getMessage());

        String jsonStr = JSONUtil.toJsonStr(result);

        try {
            response1.getWriter().print(jsonStr);
        } catch (IOException ex) {

        }
        return false;
    }

    /**
     * 解决跨域处理
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个OPTIONS请求，这里我们给OPTIONS请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }

        return super.preHandle(request, response);
    }
}
